/*
 *  Copyright 2010 Andrew Kroh
 *
 *  Kroh-IO is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Kroh-IO is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package kroh.io;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * NativeLibLoader is used to load native libraries from jar files. This allows
 * libraries to be bundled in jars and placed on the classpath rather than being
 * placed in the java.library.path.
 * 
 * <p>
 * It works by locating the resource using
 * <code>ClassLoader.getResourceAsStream()</code> and uses the returned
 * <code>InputStream</code> to extract the library to a temp file that is
 * deleted when the JVM exits.
 * 
 * <p>
 * The library should placed at the root of the jar and retain the
 * same name that it would as if it were being loaded by System.loadLibrary().
 * 
 * <p>
 * @author Andrew Kroh
 */
public class NativeLibLoader
{
    private static final Logger logger = LoggerFactory.getLogger(KrohSerialPort.class);
    
    /**
     * Loads a library from a jar file or the root of the classpath.
     * 
     * @param libname
     *          the name of the library
     *          
     * @throws UnsatisfiedLinkError
     *          if the library cannot be loaded
     */
    public static void loadLibrary(String libname)
        throws UnsatisfiedLinkError
    {
        String platformLibname = System.mapLibraryName(libname);
        
        logger.trace("Trying to load library <" + platformLibname + ">.");

        // Use classloader to locate the native library on the classpath:
        InputStream is = NativeLibLoader.class.getResourceAsStream("/" + platformLibname);
        if (is == null)
        {
            throw new UnsatisfiedLinkError("Unable to load <" + platformLibname + ">.");
        }
        
        // Extract library to a temp directory:
        File lib = null;
        FileOutputStream fos = null;
        try
        {
            lib = File.createTempFile(libname, isWindows()? ".dll" : null);
            lib.deleteOnExit();

            fos = new FileOutputStream(lib);
            int count = 0;
            byte[] buf = new byte[1024];
            while ((count = is.read(buf, 0, buf.length)) > 0) 
            {
                fos.write(buf, 0, count);
            }
        }
        catch (IOException e)
        {
            throw new UnsatisfiedLinkError("Unable to load <" + platformLibname + 
                    "> because failed to extract library to temp file. Cause: " + 
                    e.getMessage());
        }
        finally 
        {
            try { is.close(); } catch (IOException e) { }
            
            if (fos != null) 
            {
                try { fos.close(); } catch (IOException e) { }
            }
        }

        // Load the library:
        logger.trace("Loading library from <" + lib.getAbsolutePath() + ">");
        System.load(lib.getAbsolutePath());
    }
    
    private static boolean isWindows()
    {
        String osName = System.getProperty("os.name");
        Pattern WINDOWS = Pattern.compile("Windows.*", Pattern.CASE_INSENSITIVE);
        
        if (WINDOWS.matcher(osName).matches())
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
